5. 도커 개요3
2.3. Dockerfile과 이미지 빌드
이미지 구성 요소
이미지의 두 가지 구성 요소:
이미지 구성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[컨테이너 이미지]
│
├─ 루트 파일시스템
│ ├─ /bin (실행 파일)
│ ├─ /lib (라이브러리)
│ ├─ /etc (설정 파일)
│ ├─ /app (애플리케이션)
│ └─ ...
│
└─ 실행 시 설정
├─ 시작 명령어 (ENTRYPOINT/CMD)
├─ 환경 변수 (ENV)
├─ 작업 디렉토리 (WORKDIR)
└─ 실행 사용자 (USER)
도커파일 명령어는 이 두 가지를 변경함
주요 Dockerfile 명령어
명령어 카테고리별 분류:
| 명령어 | 설명 | 영향 범위 |
|---|---|---|
FROM |
베이스 이미지 지정 (항상 첫 번째 명령) | 기본 설정 |
COPY |
호스트에서 이미지로 파일 복사 | 파일시스템 변경 |
ADD |
COPY + 압축 해제 + URL 다운로드 | 파일시스템 변경 |
RUN |
빌드 시 셸 명령어 실행 | 파일시스템 변경 |
WORKDIR |
작업 디렉토리 설정 | 실행 설정 변경 |
ENV |
환경 변수 설정 | 실행 설정 변경 |
USER |
실행 사용자 지정 | 실행 설정 변경 |
EXPOSE |
컨테이너가 리스닝할 포트 문서화 | 메타데이터 |
VOLUME |
마운트 포인트 선언 | 메타데이터 |
CMD |
컨테이너 기본 실행 명령어 (덮어쓰기 가능) | 실행 설정 변경 |
ENTRYPOINT |
컨테이너 진입점 명령어 (고정) | 실행 설정 변경 |
2.3.2. 실습 예제: figlet을 사용한 Hello World 컨테이너
💡 실습 진행 전 안내:
이 실습은 본인의 실습 디렉토리에서 진행하세요.
- Windows:
C:\docker-practice또는 원하는 경로- Linux/Mac:
~/docker-practice또는 원하는 경로아래 예시 경로는 참고용이며, 본인 환경에 맞게 수정하여 진행하세요.
프로젝트 구조 이해
빌드에 필요한 구성 요소:
프로젝트 디렉토리 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
hello/
├─ hello.sh 실행할 스크립트
└─ Dockerfile 이미지 빌드 명령
[빌드 프로세스]
↓
1. 도커는 hello/ 디렉토리 전체를 "컨텍스트"로 인식
2. Dockerfile을 읽어 명령어 실행
3. 컨텍스트에서 필요한 파일 복사
4. 최종 이미지 생성
1단계: 작업 디렉토리 및 스크립트 생성
PowerShell (Windows):
# 실습 디렉토리 생성 (본인 경로로 변경)
# 예시: C:\docker-practice
New-Item -ItemType Directory -Path C:\docker-practice -Force
cd C:\docker-practice
# hello 디렉토리 생성
New-Item -ItemType Directory -Path hello -Force
# hello.sh 스크립트 파일 생성
@"
#!/bin/bash
set -eu
figlet "Hello, World!"
exec sleep infinity
"@ | Out-File -FilePath .\hello\hello.sh -Encoding UTF8
Bash (Linux/Mac):
# 실습 디렉토리 생성 (본인 경로로 변경)
# 예시: ~/docker-practice
mkdir -p ~/docker-practice
cd ~/docker-practice
# hello 디렉토리 생성
mkdir -p hello
# hello.sh 스크립트 파일 생성
cat > hello/hello.sh << 'EOF'
#!/bin/bash
set -eu
figlet "Hello, World!"
exec sleep infinity
EOF
스크립트 내용 설명:
hello.sh 분석:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#!/bin/bash
└─ Shebang(셔뱅): Bash로 실행
set -eu
└─ 엄격하게 관리
├─ -e: 에러 발생 시 즉시 종료
└─ -u: 미정의 변수 사용 시 에러
figlet "Hello, World!"
└─ ASCII 아트로 텍스트 출력
exec sleep infinity
└─ 컨테이너를 계속 실행 상태로 유지
└─ exec: 현재 프로세스를 sleep으로 대체
(PID 1로 실행되어 컨테이너 생명주기 유지)
참고: Linux 컨테이너에서 실행할 셸 스크립트이므로 내용은 bash 문법을 유지함.
Shebang (셔뱅): 스크립트 첫 줄의 #! 시퀀스. 실행할 인터프리터를 지정함 (예: #!/bin/bash).
PID 1: 컨테이너 내 첫 번째 프로세스. 이 프로세스가 종료되면 컨테이너도 종료됨.
exec: 현재 프로세스를 새 프로세스로 대체. 새 프로세스가 기존 PID를 이어받음.
figlet: 텍스트를 ASCII 아트로 변환하는 도구. 터미널 꾸미기에 사용됨.
2단계: Dockerfile 작성
hello 디렉토리에 Dockerfile 생성:
# Dockerfile 생성
@"
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y figlet dos2unix
COPY ./hello.sh /hello.sh
RUN dos2unix /hello.sh && chmod +x /hello.sh
ENTRYPOINT [ "/hello.sh" ]
"@ | Out-File -FilePath .\hello\Dockerfile -Encoding UTF8
중요:
dos2unix를 사용하여 Windows 줄바꿈(CRLF)을 Linux 줄바꿈(LF)으로 자동 변환함.
Dockerfile 명령어 상세 설명
각 명령어의 역할과 동작 원리:
Dockerfile 실행 흐름:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1] FROM ubuntu:22.04
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
베이스 이미지 지정
↓
Docker Hub에서 ubuntu:22.04 다운로드
↓
[베이스 레이어]
├─ Ubuntu 22.04 루트 파일시스템
├─ /bin, /lib, /usr 등 기본 디렉토리
└─ apt, bash 등 기본 도구
역할:
→ 모든 Dockerfile의 시작점
→ 이 이미지 위에 추가 레이어 쌓음
→ 항상 첫 번째 명령이어야 함
다른 예시:
├─ FROM python:3.11 (Python 환경)
├─ FROM node:18 (Node.js 환경)
├─ FROM golang:1.21 (Go 환경)
└─ FROM scratch (빈 이미지)
[2] ENV DEBIAN_FRONTEND=noninteractive
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
환경 변수 설정
↓
빌드 시 + 실행 시 모두 적용
↓
[환경 변수 목록]
DEBIAN_FRONTEND=noninteractive
역할:
→ apt-get이 대화형 프롬프트 표시 안 함
→ "tzdata" 같은 패키지 설치 시 입력 요구 방지
→ 자동화된 빌드에 필수
동작 원리:
[대화형 모드 - 문제]
apt-get install tzdata
↓
"시간대를 선택하세요:"
↓
사용자 입력 대기 (빌드 중단!)
[비대화형 모드 - 해결]
ENV DEBIAN_FRONTEND=noninteractive
↓
모든 입력을 기본값으로 자동 처리
↓
빌드 계속 진행
[3] RUN apt-get update && apt-get install -y figlet dos2unix
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
빌드 시 명령어 실행
↓
[새 레이어 생성]
↓
apt-get update
└─ 패키지 목록 갱신
└─ /var/lib/apt/lists/ 업데이트
↓
apt-get install -y figlet dos2unix
└─ 패키지 설치
├─ figlet: ASCII 아트 생성기
├─ dos2unix: 줄바꿈 변환 도구
└─ -y: 자동 yes 응답
↓
[결과 레이어]
└─ figlet, dos2unix 바이너리 추가
RUN 명령어 최적화:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[비효율적 - 레이어 3개 생성]
RUN apt-get update
RUN apt-get install -y figlet
RUN apt-get install -y dos2unix
[효율적 - 레이어 1개 생성]
RUN apt-get update && \
apt-get install -y figlet dos2unix
장점:
→ 레이어 수 감소 → 이미지 크기 감소
→ 캐시 무효화 최소화
[4] COPY ./hello.sh /hello.sh
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
호스트에서 이미지로 파일 복사
↓
[빌드 컨텍스트] [컨테이너 이미지]
hello/ /
├─ hello.sh ─────→ ├─ hello.sh
└─ Dockerfile ├─ bin/
├─ lib/
└─ ...
경로 이해하기:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
COPY ./hello.sh /hello.sh
│ │
│ └─ 컨테이너 내부 절대 경로
│ → 컨테이너의 루트(/)에 복사
│
└─ 빌드 컨텍스트 상대 경로
→ docker build . 실행 시
현재 디렉토리(hello/)가 컨텍스트
컨테이너의 루트(/)란?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
컨테이너는 독립된 파일시스템을 가짐:
[컨테이너 파일시스템]
/ ← 컨테이너의 루트
├─ bin/ ← 실행 파일
├─ lib/ ← 라이브러리
├─ etc/ ← 설정 파일
├─ usr/
├─ var/
├─ hello.sh ← COPY로 추가된 파일
└─ ...
호스트의 /와는 완전히 다른 공간!
COPY 명령어 패턴:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[파일 복사]
COPY ./app.py /app/app.py
소스 목적지
[디렉토리 복사]
COPY ./src/ /app/src/
디렉토리 전체 복사
[여러 파일 복사]
COPY package.json package-lock.json /app/
[모든 파일 복사]
COPY . /app/
현재 컨텍스트 전체 복사
주의사항:
→ 컨텍스트 밖 파일은 접근 불가
COPY ../outside.txt / (오류!)
→ .dockerignore로 제외 파일 지정
[5] RUN dos2unix /hello.sh && chmod +x /hello.sh
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
빌드 시 명령어 실행 (2개 연결)
↓
dos2unix /hello.sh
└─ Windows 줄바꿈 → Linux 줄바꿈 변환
├─ CRLF (\r\n) → LF (\n)
└─ 이유: PowerShell Out-File이 CRLF 생성
↓
chmod +x /hello.sh
└─ 실행 권한 부여
└─ -rwxr-xr-x (755)
줄바꿈 문제 상세:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Windows 줄바꿈 - CRLF]
#!/bin/bash\r\n
echo "test"\r\n
↑↑
CRLF
[Linux 줄바꿈 - LF]
#!/bin/bash\n
echo "test"\n
↑
LF만
문제 발생:
#!/bin/bash\r\n
↓
Linux가 \r을 인식 못함
↓
"exec format error" 발생
해결:
dos2unix 실행으로 \r 제거
[6] ENTRYPOINT [ "/hello.sh" ]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
컨테이너 시작 시 실행할 명령어 지정
↓
[컨테이너 실행 시]
docker run hello-figlet
↓
자동으로 /hello.sh 실행
↓
[PID 1]
/hello.sh
├─ figlet "Hello, World!" 출력
└─ sleep infinity 실행
ENTRYPOINT vs CMD:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[ENTRYPOINT]
→ 고정된 진입점
→ docker run 시 덮어쓰기 어려움
→ 애플리케이션이 확실할 때 사용
예시:
ENTRYPOINT ["/app/server"]
[CMD]
→ 기본 명령어 (덮어쓰기 가능)
→ docker run 시 쉽게 변경 가능
→ 유연한 실행이 필요할 때 사용
예시:
CMD ["python", "app.py"]
[조합]
ENTRYPOINT ["/app/server"]
CMD ["--port", "8080"]
↓
실행: /app/server --port 8080
↓
docker run myapp --port 3000
↓
실행: /app/server --port 3000
(CMD만 덮어씀)
표기법:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Exec 형식 - 권장]
ENTRYPOINT ["/hello.sh"]
└─ JSON 배열 형식
└─ 직접 실행 (셸 거치지 않음)
└─ 신호 처리 정확
[Shell 형식]
ENTRYPOINT /hello.sh
└─ /bin/sh -c "/hello.sh" 로 실행
└─ 셸 기능 사용 가능 ($VAR, &&)
└─ PID 1이 sh가 됨 (권장 안 함)
CRLF / LF: CRLF는 Windows 줄바꿈(\r\n), LF는 Linux/Mac 줄바꿈(\n). Linux 컨테이너에서는 LF 형식이 필요함.
.dockerignore: 빌드 컨텍스트에서 제외할 파일 목록. .gitignore와 유사한 형식.
3단계: 이미지 빌드
빌드 실행 및 확인:
# hello 디렉토리로 이동
cd hello
# 도커 이미지 빌드
docker build -t hello-figlet .
# 빌드된 이미지 확인
docker images | Select-String hello-figlet
빌드 명령어 구조 상세:
docker build 명령어 분석:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
docker build -t hello-figlet .
│ │ │ │
│ │ │ └─ 빌드 컨텍스트 경로
│ │ │ (Dockerfile 위치)
│ │ │
│ │ └─ 이미지 이름:태그
│ │ (태그 생략 시 latest)
│ │
│ └─ 태그 지정 옵션
│
└─ 이미지 빌드 명령어
빌드 컨텍스트란?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[현재 디렉토리 구조]
hello/
├─ hello.sh
├─ Dockerfile
└─ other-file.txt
docker build . 실행 시
↓
hello/ 디렉토리 전체를 도커 데몬에 전송
↓
[빌드 컨텍스트]
├─ hello.sh ← COPY 가능
├─ Dockerfile
└─ other-file.txt ← COPY 가능
COPY ./hello.sh /hello.sh
↑
컨텍스트 루트 기준
주의:
→ 컨텍스트 밖 파일은 COPY 불가
→ 큰 파일은 .dockerignore로 제외
빌드 과정 실행 흐름:
도커 빌드 단계별 실행:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Step 1/6] FROM ubuntu:22.04
↓
Docker Hub에서 이미지 다운로드
↓
[베이스 레이어]
sha256:abc123...
[Step 2/6] ENV DEBIAN_FRONTEND=noninteractive
↓
환경 변수 메타데이터 추가
↓
[메타데이터 레이어]
sha256:def456...
[Step 3/6] RUN apt-get update && apt-get install -y figlet dos2unix
↓
컨테이너 생성 → 명령 실행 → 변경사항 커밋
↓
[패키지 설치 레이어]
sha256:ghi789...
↓
figlet, dos2unix 바이너리 추가됨
[Step 4/6] COPY ./hello.sh /hello.sh
↓
빌드 컨텍스트에서 파일 복사
↓
[파일 복사 레이어]
sha256:jkl012...
↓
/hello.sh 파일 추가됨
[Step 5/6] RUN dos2unix /hello.sh && chmod +x /hello.sh
↓
컨테이너 생성 → 명령 실행 → 커밋
↓
[파일 수정 레이어]
sha256:mno345...
↓
/hello.sh 줄바꿈 변환 및 실행 권한 부여
[Step 6/6] ENTRYPOINT [ "/hello.sh" ]
↓
시작 명령어 메타데이터 설정
↓
[최종 이미지]
hello-figlet:latest
│
└─ 모든 레이어를 합친 완성된 이미지
레이어 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[hello-figlet:latest]
│
├─ 레이어 6: ENTRYPOINT 메타데이터
├─ 레이어 5: dos2unix + chmod
├─ 레이어 4: hello.sh 복사
├─ 레이어 3: figlet, dos2unix 설치
├─ 레이어 2: ENV 설정
└─ 레이어 1: ubuntu:22.04 (베이스)
각 레이어는 읽기 전용
→ 효율적 저장 (공통 레이어 재사용)
→ 캐시 활용 가능
4단계: 컨테이너 실행
실행 및 확인:
# 포그라운드로 실행 (출력 확인용)
docker run --name my-hello hello-figlet
# 백그라운드로 실행
docker run -d --name my-hello hello-figlet
# 로그 확인
docker logs my-hello
# 실행 중인 컨테이너 확인
docker ps
실행 과정 상세:
컨테이너 실행 흐름:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[이미지: hello-figlet]
↓ docker run
[컨테이너 생성]
├─ 고유 ID 할당
├─ 이름: my-hello
├─ 쓰기 가능 레이어 추가
└─ 네트워크 설정
↓
[컨테이너 시작]
↓
ENTRYPOINT ["/hello.sh"] 실행
↓
[PID 1] /hello.sh
↓
#!/bin/bash 실행
↓
figlet "Hello, World!" 출력
↓
출력:
_ _ _ _ __ __ _ _ _
| | | | ___| | | ___ \ \ / /__ _ __| | __| | |
| |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _` | |
| _ | __/ | | (_) | \ V V / (_) | | | | (_| |_|
|_| |_|\___|_|_|\___/ \_/\_/ \___/|_| |_|\__,_(_)
↓
exec sleep infinity 실행
↓
[컨테이너 계속 실행 상태]
└─ PID 1: sleep infinity
(컨테이너를 종료하지 않고 대기)
도커파일 빌드 과정 정리
전체 빌드 흐름 요약:
Dockerfile 빌드 전체 과정:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. FROM ubuntu:22.04
↓
Docker Hub에서 ubuntu:22.04 Pull
→ 베이스 이미지로 사용
→ /bin, /lib 등 기본 파일시스템 제공
2. ENV DEBIAN_FRONTEND=noninteractive
↓
환경변수 설정
→ apt-get 대화형 프롬프트 방지
→ 자동화된 빌드 가능
3. RUN apt-get update && apt-get install -y figlet dos2unix
↓
패키지 설치
→ figlet: ASCII 아트 생성
→ dos2unix: CRLF → LF 변환
4. COPY ./hello.sh /hello.sh
↓
호스트 파일을 컨테이너로 복사
→ 빌드 컨텍스트의 hello.sh
→ 컨테이너 루트(/)에 복사
5. RUN dos2unix /hello.sh && chmod +x /hello.sh
↓
파일 변환 및 권한 부여
→ Windows 줄바꿈을 Linux 형식으로 변환
→ 실행 권한 추가 (755)
6. ENTRYPOINT [ "/hello.sh" ]
↓
컨테이너 시작 명령 설정
→ docker run 시 /hello.sh 자동 실행
→ PID 1로 실행되어 컨테이너 생명주기 유지
결과:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
→ hello-figlet 이미지 생성 완료
→ "Hello, World!" ASCII 아트 출력 기능
→ 영구 실행 상태 유지 (sleep infinity)
정리 명령어
PowerShell (Windows):
# 컨테이너 중지 및 삭제
docker stop my-hello
docker rm my-hello
# 이미지 삭제
docker rmi hello-figlet
# 작업 디렉토리 삭제 (선택사항)
cd ..
Remove-Item -Recurse -Force hello
Bash (Linux/Mac):
# 컨테이너 중지 및 삭제
docker stop my-hello
docker rm my-hello
# 이미지 삭제
docker rmi hello-figlet
# 작업 디렉토리 삭제 (선택사항)
cd ..
rm -rf hello
2.3.3. 멀티 스테이지 빌드
멀티 스테이지 빌드 개념
이미지 경량화를 위한 핵심 기법:
멀티 스테이지 빌드의 필요성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[일반 빌드의 문제]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FROM golang:1.21
COPY . /app
RUN go build -o /app/hello
↓
[최종 이미지: 1.2GB]
├─ Go 컴파일러 (불필요)
├─ Go 도구 체인 (불필요)
├─ 소스 코드 (불필요)
└─ hello 바이너리 (필요!)
문제:
→ 빌드 도구가 최종 이미지에 포함
→ 이미지 크기 과다
→ 보안 위험 증가
→ 배포 시간 증가
[멀티 스테이지 빌드 - 해결]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[빌드 스테이지]
FROM golang:1.21 AS builder
COPY . /app
RUN go build -o /hello
↓
hello 바이너리 생성
(이 스테이지는 버려짐)
[실행 스테이지]
FROM scratch
COPY --from=builder /hello /hello
↓
[최종 이미지: 2MB]
└─ hello 바이너리만 포함
장점:
→ 빌드 도구 제외
→ 이미지 크기 600배 감소
→ 보안 공격 표면 최소화
→ 배포 속도 향상
스테이지 개념 이해
여러 FROM 명령으로 독립적인 빌드 단계 구성:
멀티 스테이지 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Dockerfile]
│
├─ [스테이지 1: dev]
│ FROM golang:1.21.3 AS dev
│ COPY . /root/hello/
│ RUN go build -o /hello /root/hello/hello.go
│ ↓
│ 독립적인 빌드 환경
│ 빌드 도구 포함
│
└─ [스테이지 2: 최종]
FROM scratch
COPY --from=dev /hello .
ENTRYPOINT [ "/hello" ]
↓
실행 환경만 포함
빌드 산출물만 복사
스테이지 간 통신:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
COPY --from=스테이지명 소스 목적지
│ │ │
│ │ └─ 현재 스테이지 경로
│ │
│ └─ 이전 스테이지 경로
│
└─ 다른 스테이지에서 복사
특징:
→ 각 스테이지는 독립적으로 빌드
→ 마지막 스테이지만 최종 이미지로 생성
→ 중간 스테이지는 버려짐
실습 예제: Go 애플리케이션 빌드
💡 실습 경로: 앞서 생성한 실습 디렉토리 내에서 진행하세요.
- Windows 예시:
C:\docker-practice- Linux/Mac 예시:
~/docker-practice
프로젝트 준비:
PowerShell (Windows):
# 실습 디렉토리로 이동 (본인 경로로 변경)
cd C:\docker-practice
# hello-go 디렉토리 생성
New-Item -ItemType Directory -Path hello-go -Force
cd hello-go
Bash (Linux/Mac):
# 실습 디렉토리로 이동 (본인 경로로 변경)
cd ~/docker-practice
# hello-go 디렉토리 생성
mkdir -p hello-go
cd hello-go
Go 소스 코드 작성:
PowerShell (Windows):
# hello.go 파일 생성
@"
package main
import "fmt"
func main() {
fmt.Println("hello")
}
"@ | Out-File -FilePath .\hello.go -Encoding UTF8
Bash (Linux/Mac):
# hello.go 파일 생성
cat > hello.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("hello")
}
EOF
go.mod 파일 생성:
PowerShell (Windows):
# go.mod 파일 생성
@"
module hello
go 1.21.3
"@ | Out-File -FilePath .\go.mod -Encoding UTF8
Bash (Linux/Mac):
# go.mod 파일 생성
cat > go.mod << 'EOF'
module hello
go 1.21.3
EOF
멀티 스테이지 Dockerfile 작성:
PowerShell (Windows):
# Dockerfile 생성 (확장자 없이)
@"
FROM golang:1.21.3 AS dev
COPY . /root/hello/
RUN go build -o /hello /root/hello/hello.go
FROM scratch
COPY --from=dev /hello .
ENTRYPOINT [ "/hello" ]
"@ | Out-File -FilePath .\Dockerfile -Encoding UTF8 -NoNewline
Bash (Linux/Mac):
# Dockerfile 생성
cat > Dockerfile << 'EOF'
FROM golang:1.21.3 AS dev
COPY . /root/hello/
RUN go build -o /hello /root/hello/hello.go
FROM scratch
COPY --from=dev /hello .
ENTRYPOINT [ "/hello" ]
EOF
Dockerfile 구조 상세 분석:
멀티 스테이지 Dockerfile 분석:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[스테이지 1: dev - 빌드 환경]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FROM golang:1.21.3 AS dev
│ │
│ └─ 스테이지 이름 지정
│ 다른 스테이지에서 참조 가능
│
└─ Go 컴파일러 포함 이미지 (약 1GB)
├─ Go 도구 체인
├─ 컴파일러
└─ 표준 라이브러리
COPY . /root/hello/
│ │
│ └─ 컨테이너 내부 경로
│ /root/hello/ 디렉토리에 복사
│
└─ 현재 컨텍스트 전체
├─ hello.go
├─ go.mod
└─ Dockerfile (자동 제외)
RUN go build -o /hello /root/hello/hello.go
│ │ │
│ │ └─ 소스 파일 경로
│ │
│ └─ 출력 바이너리 경로
│ /hello (루트에 생성)
│
└─ 출력 파일명 지정
결과:
→ /hello 실행 파일 생성
→ 이 스테이지는 최종 이미지에 포함 안 됨
[스테이지 2: 최종 - 실행 환경]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FROM scratch
│
└─ 빈 이미지 (0 byte)
→ 파일시스템 없음
→ OS 도구 없음
→ 바이너리만 실행 가능
scratch란?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[일반 베이스 이미지]
ubuntu:22.04
│
├─ /bin (bash, ls, cat 등)
├─ /lib (라이브러리)
├─ /etc (설정)
└─ 기본 도구들
크기: 약 80MB
[scratch]
완전히 빈 상태
│
└─ 아무것도 없음
크기: 0 byte
사용 가능 조건:
→ 정적 링크된 바이너리
→ C 라이브러리 의존성 없음
→ Go, Rust 같은 언어 적합
COPY --from=dev /hello .
│ │ │
│ │ └─ 현재 스테이지 루트(/)
│ │
│ └─ dev 스테이지의 /hello 파일
│
└─ dev 스테이지에서 복사
결과:
[최종 이미지 파일시스템]
/
└─ hello (실행 파일만 존재)
ENTRYPOINT [ "/hello" ]
↓
컨테이너 실행 시 /hello 바이너리 실행
scratch: 완전히 빈 도커 이미지 (0바이트). 정적 바이너리만 실행 가능함.
정적 링크 (Static Linking): 실행에 필요한 라이브러리를 바이너리에 포함. 외부 라이브러리 의존성 없이 독립 실행 가능함.
이미지 빌드 및 실행:
# 이미지 빌드
docker build -t hello-go .
# 컨테이너 실행
docker run --rm hello-go
# 출력:
# hello
명령어 옵션 설명:
docker run --rm 옵션:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
docker run --rm hello-go
│
└─ 컨테이너 종료 시 자동 삭제
[--rm 없이 실행]
docker run --name test hello-go
↓
컨테이너 종료 후에도 유지
↓
docker ps -a # 중지된 컨테이너 목록에 남음
↓
수동 삭제 필요: docker rm test
[--rm으로 실행]
docker run --rm hello-go
↓
컨테이너 종료와 동시에 자동 삭제
↓
docker ps -a # 목록에 없음
사용 시나리오:
→ 일회성 작업 (빌드, 테스트)
→ 로그 확인용 실행
→ 디버깅 실행
--rm 옵션: docker run의 옵션. 컨테이너 종료 시 자동 삭제됨.
이미지 크기 비교
빌드 스테이지 vs 최종 스테이지:
# dev 스테이지 이미지 빌드
docker build -t hello-go-dev --target dev .
# 이미지 크기 비교
docker images | Select-String "hello-go"
# 출력 예시:
# hello-go latest abc123 2.5MB
# hello-go-dev latest def456 1.2GB
--target 옵션 설명:
--target 옵션 사용:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
docker build --target 스테이지명 .
│
└─ 특정 스테이지까지만 빌드
[Dockerfile 구조]
FROM golang:1.21.3 AS dev ← 스테이지 1
...
FROM scratch ← 스테이지 2 (최종)
...
[기본 빌드]
docker build -t hello-go .
↓
마지막 스테이지까지 빌드 (scratch)
↓
최종 이미지: 2.5MB
[dev 스테이지 빌드]
docker build -t hello-go-dev --target dev .
↓
dev 스테이지까지만 빌드
↓
dev 이미지: 1.2GB
사용 시나리오:
→ 디버깅용 이미지 (dev 스테이지)
→ 테스트 실행 (test 스테이지)
→ 빌드 캐시 사전 생성
--target 옵션: 멀티 스테이지 빌드에서 특정 스테이지까지만 빌드. 디버깅, 테스트용 이미지 생성에 활용됨.
이미지 크기 비교 분석:
이미지 크기 차이:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[hello-go-dev: 1.2GB]
├─ golang:1.21.3 베이스 (약 1GB)
│ ├─ Go 컴파일러
│ ├─ Go 도구 체인
│ ├─ 표준 라이브러리
│ └─ Linux 기본 도구
│
├─ 소스 코드 (hello.go, go.mod)
└─ 컴파일된 hello 바이너리
[hello-go: 2.5MB]
└─ hello 바이너리만
└─ 정적 링크된 실행 파일
크기 감소율: 약 480배!
실무 효과:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[배포 속도]
1.2GB 다운로드: 약 2-5분
2.5MB 다운로드: 약 1-2초
[보안]
큰 이미지: 공격 표면 넓음
작은 이미지: 취약점 최소화
[스토리지]
1000개 Pod: 1.2TB vs 2.5GB
멀티 스테이지 빌드의 병렬 실행
BuildKit의 최적화 기능:
도커 v23.0.0부터 docker build의 백엔드로 사용하는 BuildKit은 멀티 스테이지 도커파일을 효율적으로 빌드함. BuildKit은 도커파일에 기록된 명령 간의 의존 관계를 분석해서 최소한으로 필요한 스테이지만 실행하며, 이런 실행도 가급적 병렬로 실행함.
병렬 빌드 예제:
PowerShell (Windows):
# figlet을 포함한 Go 애플리케이션 Dockerfile
@"
FROM golang:1.21.3 AS dev
COPY . /root/hello/
RUN go build -o /hello /root/hello/hello.go
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y figlet dos2unix
COPY --from=dev /hello .
ENTRYPOINT [ "/bin/sh", "-euc", "/hello | figlet" ]
"@ | Out-File -FilePath .\Dockerfile -Encoding UTF8 -NoNewline
Bash (Linux/Mac):
# figlet을 포함한 Go 애플리케이션 Dockerfile
cat > Dockerfile << 'EOF'
FROM golang:1.21.3 AS dev
COPY . /root/hello/
RUN go build -o /hello /root/hello/hello.go
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y figlet dos2unix
COPY --from=dev /hello .
ENTRYPOINT [ "/bin/sh", "-euc", "/hello | figlet" ]
EOF
병렬 실행 분석:
BuildKit 병렬 빌드 최적화:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Dockerfile 의존성 분석]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
스테이지 1: dev
├─ FROM golang:1.21.3 AS dev
├─ COPY . /root/hello/
└─ RUN go build -o /hello /root/hello/hello.go
스테이지 2: 최종
├─ FROM ubuntu:22.04
├─ ENV DEBIAN_FRONTEND=noninteractive
├─ RUN apt-get update && apt-get install -y figlet dos2unix
├─ COPY --from=dev /hello . ← dev 스테이지 의존
└─ ENTRYPOINT [ "/bin/sh", "-euc", "/hello | figlet" ]
[병렬 실행 그룹]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[타임라인]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
시간 →
[병렬 그룹 1 - 독립적 실행]
┌────────────────────────────┐
│ dev 스테이지 │
│ FROM golang:1.21.3 │
│ COPY . /root/hello/ │
│ RUN go build... │
└────────────────────────────┘
↓ (빌드 완료 대기)
┌────────────────────────────┐ ← 동시 실행!
│ 최종 스테이지 (초기 부분) │
│ FROM ubuntu:22.04 │
│ ENV DEBIAN_FRONTEND=... │
│ RUN apt-get install figlet │
└────────────────────────────┘
↓ (figlet 설치 완료)
[병렬 그룹 2 - 의존성 대기]
↓ (dev 스테이지 완료 후)
┌────────────────────────────┐
│ COPY --from=dev /hello . │ ← dev 완료 필요
│ ENTRYPOINT [ ... ] │
└────────────────────────────┘
최적화 효과:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[순차 실행 (최적화 없음)]
dev 빌드 (30초)
↓
ubuntu Pull (10초)
↓
figlet 설치 (20초)
↓
복사 및 설정 (2초)
━━━━━━━━━━━━━━━━
총 시간: 62초
[병렬 실행 (BuildKit)]
dev 빌드 (30초) ║ ubuntu + figlet (30초)
↓
복사 및 설정 (2초)
━━━━━━━━━━━━━━━━
총 시간: 32초 (약 50% 단축!)
의존성 분석 원리:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
COPY --from=dev /hello .
↑
dev 스테이지 완료 필수
RUN apt-get install figlet
↑
dev와 무관 → 병렬 실행 가능!
FROM ubuntu:22.04
↑
dev와 무관 → 병렬 실행 가능!
BuildKit의 추가 최적화 기능:
BuildKit 고급 기능:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1] 캐시 마운트
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RUN --mount=type=cache,target=/go/pkg/mod \
go build -o /app/main .
↓
/go/pkg/mod를 캐시로 유지
↓
다음 빌드 시 의존성 재다운로드 불필요
[2] 시크릿 마운트
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
aws s3 cp s3://bucket/file .
↓
시크릿을 임시로만 마운트
↓
최종 이미지에 포함 안 됨 (보안!)
[3] SSH 마운트
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RUN --mount=type=ssh \
git clone git@github.com:private/repo.git
↓
SSH 키를 안전하게 사용
↓
이미지에 키 남지 않음
[4] 멀티 플랫폼 빌드
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
docker buildx build --platform linux/amd64,linux/arm64 -t myapp .
↓
한 번에 여러 아키텍처용 이미지 생성
↓
Intel + ARM 동시 지원
BuildKit: 도커의 차세대 이미지 빌드 엔진. 병렬 빌드, 캐시 최적화 등을 제공함.
핵심 요약
도커파일 및 멀티 스테이지 빌드 정리:
2.3장 핵심 개념:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Dockerfile 기본]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
주요 명령어:
├─ FROM: 베이스 이미지 지정
├─ COPY: 파일 복사 (컨텍스트 → 이미지)
├─ RUN: 빌드 시 명령 실행 (레이어 생성)
├─ ENV: 환경 변수 설정
├─ WORKDIR: 작업 디렉토리 지정
├─ EXPOSE: 포트 문서화
└─ ENTRYPOINT/CMD: 시작 명령어
빌드 컨텍스트:
→ docker build . 실행 시
→ 현재 디렉토리 전체가 컨텍스트
→ COPY 명령으로 접근 가능
→ .dockerignore로 제외 가능
레이어 구조:
→ 각 명령어마다 새 레이어 생성
→ 읽기 전용으로 캐시
→ 변경 시 이후 레이어 재생성
경로 이해:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
COPY ./file.txt /app/file.txt
│ │
│ └─ 컨테이너 내부 절대 경로
│ (컨테이너의 루트 기준)
│
└─ 호스트 상대 경로
(빌드 컨텍스트 기준)
컨테이너 루트(/):
→ 컨테이너 독립 파일시스템의 최상위
→ 호스트의 /와는 완전히 별개
→ /bin, /lib, /etc 등 포함
[멀티 스테이지 빌드]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
개념:
→ 여러 FROM 명령으로 독립 스테이지 구성
→ 각 스테이지는 독립적으로 빌드
→ 최종 스테이지만 이미지로 생성
구조:
┌─────────────────────────────┐
│ 빌드 스테이지 │
│ FROM golang:1.21 AS builder │
│ RUN go build -o /app │
└─────────────────────────────┘
↓ (바이너리만 복사)
┌─────────────────────────────┐
│ 실행 스테이지 │
│ FROM scratch │
│ COPY --from=builder /app . │
└─────────────────────────────┘
장점:
✓ 이미지 크기 극소화 (수백 배 감소)
✓ 빌드 도구 제외 → 보안 강화
✓ 배포 속도 향상
✓ 스토리지 절약
BuildKit 최적화:
→ 의존성 분석으로 병렬 빌드
→ 불필요한 스테이지 스킵
→ 캐시 활용 최적화
→ 빌드 시간 단축
[실무 베스트 프랙티스]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 레이어 최소화:
RUN apt-get update && apt-get install -y pkg
2. 자주 변경되는 파일은 하단 배치:
COPY package.json /app/ (의존성)
RUN npm install (캐시 활용)
COPY . /app/ (소스 코드)
3. .dockerignore 활용:
node_modules/
.git/
*.log
4. 멀티 스테이지 활용:
빌드 스테이지 + 실행 스테이지 분리
5. 명시적 버전 지정:
FROM ubuntu:22.04 (latest 피함)
6. 보안:
→ 최소 권한 USER 지정
→ 불필요한 도구 제외
→ 시크릿 하드코딩 금지
결론:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
→ Dockerfile = 이미지 청사진 (IaC)
→ 레이어 구조 이해 → 최적화
→ 멀티 스테이지 = 경량화 핵심
→ BuildKit = 빌드 속도 향상
→ 체계적 접근 = 효율적 이미지
참고 자료
공식 문서:
- Dockerfile Reference: https://docs.docker.com/engine/reference/builder/
- Multi-stage Builds: https://docs.docker.com/build/building/multi-stage/
- BuildKit: https://docs.docker.com/build/buildkit/
베스트 프랙티스:
- Dockerfile Best Practices: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
- Build Cache: https://docs.docker.com/build/cache/